package thread;

/**
 * 同步块
 * 有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率
 *
 * 语法:
 * synchronized(同步监视器对象){
 *     需要同步执行的代码片段
 * }
 */
public class SyncDemo2 {
    public static void main(String[] args) {
//        Shop shop = new Shop();
        Shop shop1 = new Shop();
        Shop shop2 = new Shop();
//        最土写法:
//        Thread t1 = new Thread("王克晶"){
//            public void run(){
//                shop1.buy();
//            }
//        };
//        Thread t2 = new Thread("范传奇"){
//            public void run(){
//                shop2.buy();
//            }
//        };

//        往lambda进化
//        Runnable r = new Runnable() {
//            public void run() {
//                shop.buy();
//            }
//        };
//        Thread t1 = new Thread(r,"王克晶");

//        Runnable可以使用lambda
//        Runnable r = ()->{
//                shop.buy();
//        };
//        Thread t1 = new Thread(r,"王克晶");

//        lambda再次简化
//        Runnable r = ()->shop.buy();
//        Thread t1 = new Thread(r,"王克晶");

//        直接将lambda创建的Runnable作为参数传递给Thread
//        Thread t1 = new Thread(()->shop.buy(),"王克晶");

//        由于lambda的参数与调用的方法参数一致，返回值一致，因此可以用方法引用
/*
          ()->shop.buy()   该lambda对应的原始匿名内部类
          new Runnable(){
            public void run(){  lambda表达式对应的是Runnable的run方法
                shop.buy();
            }
          }
          由于lambda表达的run方法的返回值为void，参数列表为无参
          而run中调用的方法shop.buy()方法返回值也是void,参数列表也是无参
          因此可以理解为lambda表达式表达的run方法的返回值，参数列表，与该方法
          内部调用的shop.buy方法的返回值和参数列表一致。
          因此()->shop.buy()  就可以写成 shop::buy

 */
//        Thread t1 = new Thread(shop::buy,"王克晶");

//        new Thread(shop::buy,"王克晶").start();
//        new Thread(shop::buy,"范传奇").start();
        new Thread(shop1::buy,"王克晶").start();
        new Thread(shop2::buy,"范传奇").start();
    }
}

class Shop{
    /*
        在成员方法上使用synchronized时，也有同步监视器对象，这个对象不可选，只能
        是this。
     */
//    public synchronized void buy(){
    public void buy(){
        try {
            Thread t = Thread.currentThread();
            System.out.println(t.getName()+":正在挑衣服...");
            Thread.sleep(5000);
            /*
                使用同步块可以更精准的锁定需要多个线程排队执行的代码片段
                同步块需要指定同步监视器对象,即:"()"中要指定一个对象
                对象的选取原则：
                1:多个需要排队执行该代码片段的线程看到的【必须是同一个】同步监视器对象
                2:同步监视器对象是一个引用类型

                选取合适的同步监视器对象
                存在并发安全问题时应当发挥效果(同步执行)，不存在并发安全问题是应当
                失效。
                例如:
                T1和T2线程如果分别执行两个不同的shop对象的buy方法时，执行下述代码
                就不应当排队进行。此时选取this是合适的。
                如果选取的是"abc"(字符串的字面量)，任何时候字符串字面量都是同一个
                对象，因此如果选取它就不合适了。

                选取锁对象时:
                1:不应当有new关键字
                2:不应当选取字符串字面量
             */
            synchronized (this) {
//            synchronized ("abc") {
//            synchronized (new Object()) {
                System.out.println(t.getName() + ":正在试衣服...");
                Thread.sleep(5000);
            }

            System.out.println(t.getName()+":结账离开");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}












